﻿using System;
using System.Linq;
using System.Collections.Generic;

using System.Net;
using System.Net.Sockets;

using AnB.Common;

namespace AnB.Net
{
    public class Server : NetObject
    {
        public Server(Type connectionClass, int backlog = DEFAULT_BACKLOG, params object[] connectionArgs) : base("*server*")
        {
            mConnectionClass = connectionClass;
            mBacklog = backlog;
            mConnectionArgs = connectionArgs;
        }

        public const int DEFAULT_BACKLOG = 10;
        private int mBacklog;
        private object[] mConnectionArgs;

        private Socket mSocket;

        private Type mConnectionClass;
        public Type ConnectionClass
        {
            get
            {
                return mConnectionClass;
            }
        }

        private Dictionary<string, ConnectionBase> mConnections = new Dictionary<string, ConnectionBase>();

        protected void Start(IPEndPoint[] eps)
        {
            Alias = string.Join(",", eps.Select(ep => ep.ToString()));
            mSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            foreach (var ep in eps)
            {
                Log.Get().InfoFormat("{0} Bind {1}", Alias, ep.ToString());
                mSocket.Bind(ep);
            }
            Log.Get().InfoFormat("{0} Listen backlog {1}", Alias, mBacklog);
            mSocket.Listen(mBacklog);

            Accept();
        }
        public void Start(IPEndPoint ep)
        {
            Start(new IPEndPoint[] { ep });
        }
        protected void Start(IPAddress[] addresses, int port)
        {
            Start(addresses.Select(address => new IPEndPoint(address, port)).ToArray());
        }
        public void Start(IPAddress address, int port)
        {
            Start(new IPAddress[] { address }, port);
        }
        public void Start(string host, int port)
        {
            Start(Dns.GetHostAddresses(host), port);
        }
        public void Start(int port)
        {
            Start(new IPEndPoint(IPAddress.Any, port));
        }
        public override void Stop()
        {
            base.Stop();
            if (mSocket != null)
            {
                mSocket.Close();
            }
        }

        private void Accept()
        {
            try
            {
                mSocket.BeginAccept(Accepted, mSocket);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} BeginAccept failed {0}\n{1}", Alias, e.Message, e.StackTrace);
                mSocket.Close();
                throw;
            }
        }

        public delegate void OnAcceptedAction(ConnectionBase connection);
        public OnAcceptedAction OnAccepted;

        private void Accepted(IAsyncResult result)
        {
            var serverSocket = (Socket)result.AsyncState;
            Socket socket;
            try
            {
                socket = serverSocket.EndAccept(result);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} EndAccept {1}\n{2}", Alias, e.Message, e.StackTrace);
                socket = null;
                //return;
            }
            if (!IsStop)
            {
                Accept();
            }
            if (socket == null)
            {
                return;
            }
            Log.Get().InfoFormat("{0} accepted {1}", Alias, socket.RemoteEndPoint.ToString());

            var connection = CreateConnection(socket);
            var remote = (IPEndPoint)socket.RemoteEndPoint;
            string key = remote.ToString();
            mConnections[key] = connection;
            connection.OnStop += (c) =>
                {
                    mConnections.Remove(key);
                };

            connection.Start();
            if (OnAccepted != null)
            {
                OnAccepted(connection);
            }
        }
        protected ConnectionBase CreateConnection(Socket socket)
        {
            return (ConnectionBase)Activator.CreateInstance(mConnectionClass, Utils.Flat(socket, mConnectionArgs));
        }

    }
}
